package hcs.model;

import hcs.HouseController;
import hcs.interfaces.RoomManagementStrategy;
import hcs.interfaces.StateObservable;

import stubs.IPhysicalRoom;

/**
 * A proxy class registered on {@link Clock}, which polls
 * {@link PhysicalRoom} on every {@link Clock#tic}
 */
public class Room extends StateObservable {
	/**
	 * The type representing window status
	 */
	public enum WindowState {OPEN, CLOSED, TILTED};
	private String name;
	private int sqm;
	private HouseController houseController;
	private IPhysicalRoom physicalRoom;
	private double temperature;
	private WindowState windowState;
	private boolean presence;
	private boolean manualWindow;
	private double heaterTarget;
	private double heaterCurrent;
	private RoomSettings roomSettings;
	private long lastUpdateTime;
	private long inTime, outTime;
	private Environment env;
	
	// strategies
	private RoomManagementStrategy awayStrategy;
	private RoomManagementStrategy presenceStrategy;
	private RoomManagementStrategy currentStrategy;
	
	/**
	 * Class constructor
	 * @param name the name of the room
	 * @param sqm the sqm of the room
	 * @param pR the {@link PhysicalRoom} to poll
	 * @param hC the {@link HouseController} object
	 */
	public Room(String name, int sqm, IPhysicalRoom pR, HouseController hC) {
		this.name = name;
		this.sqm = sqm;
		this.houseController = hC;
		this.env = hC.getEnvironment();
		this.physicalRoom = pR;
		temperature = physicalRoom.getTemperature();
		presence = physicalRoom.presenceSwitchOpen(); // we assume the user is inside/outside the room from an undefined (but long enough) time.
		manualWindow = false;
		windowState = decodeWindowState();
		heaterTarget = physicalRoom.getHeaterTarget();
		heaterCurrent = physicalRoom.getHeaterTarget();
		roomSettings = new RoomSettings(this);
		roomSettings.revertToDefault();
		lastUpdateTime = env.getSecondsSinceStart();
		inTime = 0;
		outTime = 0;

		awayStrategy = new AwayManagementStrategy(this);
		presenceStrategy = new PresenceManagementStrategy(this);
		if (presence) {
			currentStrategy = presenceStrategy;
		} else {
			currentStrategy = awayStrategy;
		}
	}
	
	private WindowState decodeWindowState() {
		if (physicalRoom.tiltedWindowSwitchOpen()) {
			return WindowState.TILTED;
		}
		if (physicalRoom.closedWindowSwitchOpen()) {
			return WindowState.OPEN;
		}
		return WindowState.CLOSED;
	}
	
	/**
	 * 
	 * @return the current window status
	 */
	public WindowState getWindowStatus() {
		return windowState;
	}
	
	/**
	 * Set the new window status, which can be choosen from the {@link WindowState} enum.
	 * @param state the new state of the window.
	 */
	public void setWindowStatus(WindowState state) {
		if (state.equals(WindowState.CLOSED)) {
			if (getWindowStatus().equals(WindowState.OPEN)) {
				physicalRoom.swingWindow();
				this.setChanged();
			}
			if (getWindowStatus().equals(WindowState.TILTED)) {
				physicalRoom.tiltWindow();
				this.setChanged();
			}
			/* BACO: Al posto dei due IF precedenti, scrivere questo singolo if
			 * (che al posto di chiudere la finestra se questa è tilted, la apre completamente)
			 * if (!getWindowStatus().equals(WindowState.CLOSED)) {
			 * 		pyhisicalRoom.swingWindow();
			 * }
			 */
		}
		if (state.equals(WindowState.OPEN)) {
			if (!getWindowStatus().equals(WindowState.OPEN)) {
				physicalRoom.swingWindow();
				this.setChanged();
			}
		}
		if (state.equals(WindowState.TILTED)) {
			if (!getWindowStatus().equals(WindowState.TILTED)) {
				physicalRoom.tiltWindow();
				this.setChanged();
			}
		}
		this.notifyObservers();
	}
	
	/**
	 * 
	 * @return the current room temperature
	 */
	public double getTemperature() {
		return temperature;
	}

	/**
	 * Force polling
	 */
	public void poll() {
		// retrieve the current status
		long currentTime = env.getSecondsSinceStart();
		long elapsedTime = currentTime - lastUpdateTime;
		WindowState ws = decodeWindowState();
		boolean p = this.physicalRoom.presenceSwitchOpen();
		
		// check the "manual Window" status
		if (manualWindow) { // the window is manullay handled by the user
			if (!ws.equals(WindowState.OPEN)) {
				// the user has closed/tilted the window, we can get window control back
				manualWindow = false;
			}
		} else { // the window is automatically handled
			if (ws.equals(WindowState.OPEN) && !windowState.equals(WindowState.OPEN)) {
				// if the window is currently opened, and last time we set it, the window was not opened,
				// then the user opened it. We are in manualWindow state
				manualWindow = true;
			}
		}
		
		// check the new state (presence/away)
		if (presence) { // the user was in the room last time we checked
			if (p) { // the user is still here
				inTime += elapsedTime;
				// check if we must activate the presence strategy
				if (inTime > roomSettings.getStandardInsideTime()*60) {
					// we must activate the presence strategy
					if (!manualWindow) {
						currentStrategy = presenceStrategy;
					} else {
						currentStrategy = awayStrategy; 
					}
				}
			} else { // the user just exited
				outTime = 0;
				this.setChanged(); // we shall notify our observers
			}
		} else { // the user was out of the room last time we checked
			if (!p) { // the user is still out
				outTime += elapsedTime;
				// check if we must activate the away strategy
				if (outTime > roomSettings.getStandardAwayTime()*60) {
					// we must activate the away strategy
					currentStrategy = awayStrategy;
				}
			} else { // the user just entered the room
				inTime = 0;
				this.setChanged(); // we shall notify our observers
			}
		}

		// update the used values
		presence = p;
		lastUpdateTime = currentTime;
		if (!ws.equals(windowState)) {
			this.setChanged();
			windowState = ws;
		}
		
		// update other values
		double t = this.physicalRoom.getTemperature();
		if (t!=temperature) {
			this.setChanged();
			temperature = t;
		}

		double hCur = this.physicalRoom.getHeaterCurrent();
		if (hCur!=heaterCurrent) {
			this.setChanged();
			heaterCurrent = hCur;
		}
		
		// apply the current strategy
		currentStrategy.applyStrategy(manualWindow);
		// notify everyone about the changes
		this.notifyObservers();
	}
	
	
	/**
	 * 
	 * @return the room name
	 */
	public String getName() {
		return this.name;
	}
	
	/**
	 * 
	 * @return the room sqm
	 */
	public int getSqm() {
		return this.sqm;
	}
	
	/**
	 * 
	 * @return true if somebody is in the room
	 */
	public boolean SomebodyHere() {
		return this.presence;
	}
	
	/**
	 * Gets the target heater temperature.
	 * @return the target heater temperature.
	 */
	public double getHeaterTarget() {
		return heaterTarget;
	}
	
	/**
	 * Gets the current heater temperature.
	 * @return the current heater temperature.
	 */
	public double getHeaterCurrent() {
		return heaterCurrent;
	}
	
	/**
	 * Sets the target heater temperature.
	 * @param temperature the target heater temperature.
	 */
	public void setHeaterTarget(double temperature) {
		if (heaterTarget!=temperature) {
			this.setChanged();
			heaterTarget = temperature;
			physicalRoom.setHeaterTarget(temperature);
			this.notifyObservers();
		}
	}
	
	/**
	 * @return {@link RoomSettings} object
	 */
	public RoomSettings getSettings() {
		return this.roomSettings;
	}
	
	/**
	 * 
	 * @return the {@link HouseController} object.
	 */
	public HouseController getHouseController() {
		return this.houseController;
	}
}
